package com.area42games.wave.robots;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import com.area42games.grammar.DiceParserDriver;
import com.area42games.grammar.trees.EvalContext;
import com.google.wave.api.AbstractRobotServlet;
import com.google.wave.api.Blip;
import com.google.wave.api.Event;
import com.google.wave.api.EventType;
import com.google.wave.api.RobotMessageBundle;
import com.google.wave.api.TextView;
import com.google.wave.api.Wavelet;

public class DiceServlet extends AbstractRobotServlet
{
	/**
	 * 
	 */
	private static final long serialVersionUID = -4883868345496063458L;
	private static final String help_message =
		"Dicey Help\n\n" +

		"Dicey understands the following commands:\n\n"+

		"Help(), Donate(), Feedback(), Roll() and Set()\n\n" +

		"Help() produces this message\n" +
		"Donate() produces a message that will tell you how you can support Dicey." +
		"Dicey runs on Google's Appspot application hosting and if the daily quota gets exceeded then Dicey would be shut down for a time.  " +
		"If you wish to help out, the Donate() command will tell you how.\n" +
		"Feedback() opens a new private wave in which you can give praise, ask questions or report bugs to the maintainers of Dicey.\n"+
		"Roll() is designed to execute simple math as well as execute the rolling of dice\n" +
		"Set() is designed to store values or macros to be used inside of Roll() commands by means of variable names\n" +
		"One option that you can set is wether the dice rolls are replies or inline.  To change it for an entire wave, enter this command: Set(Area42Dice.Style=Inline) or to set it back to the default Set(Area42Dice.Style=Reply)" +
		"\n\n"+
		"Examples:\n Roll(1d20), roll ( 3 d 6 ), ROLL (1d10 + 5)\n" +
		"Roll(6@3d6) to execute a number of identical rolls with separate sums, use the @ symbol\n" +
		"Roll(1d20+10; 1d20+5; 1d8+2; 1d8+2) to execute a number of different rolls, seperate them with semi-colons.\n" +
		"Set($target=20) Roll($target-(1d20+15)) The set command will set a particular variable name (must start with a letter) to any expression including a constant.  Reference that expression later by means of the $ operator.\n" +
		"Roll(1d20+15>25) will return a 1 or a 0 for true or false.  Currently Dicey also supports < and = signs as well.\n" +
		"To give a more complex example:  Roll(4@1d10>6) will roll a ten sided die, check to see if it is greater than six, returning 1 if it is and 0 if it is not.  It will do this four time and give you a number between zero and four.\n" +
		"Or how about Roll(6@4d6 high 3) to generate a character for many a popular game system? Also available immediately after a 'd' operation is 'low' 'reroll' and 'rollagain'. E.G. Roll(4d10 rollagain 8..10) will roll more dice for each 8, 9 or 10 rolled in the initial four.  The 'high' and 'low' operations take a single number after them as an argument indicating how many dice to keep.  The 'reroll' and 'rollagain' operations can take a single number or a range.   A range is indicated by two dots, periods or decimals, however you wish to call them.  Roll(3d6 reroll 2) will only reroll 2s.  Roll(3d6 reroll 1..2) will reroll ones and twos.\n\n"+
		"One final option, if you need special numbering on your dice or to have a lookup table of dice roll values to other values you can use this really complication operator.  This is not for the faint of heart.  Roll(4d10 rollagain 10 {1:-1, 2..5:0, 6..10:1} ) will roll four ten sided dice, roll another die for every ten rolled then look at the dice results and map them according to the setup in the {}.  A one becomes a negative one '1:-1', then two through five becomes a zero '2..5:0', and finally six through ten become one '6..10:1'.  This could also be written Roll(4d10 rollagain 10 {-1,0,0,0,0,1,1,1,1} ).  When the {} does not have ranges specified it takes the position of the value as the range.  If this doesn't make sense, message me on wave and I will try to explain it better. "+
		"\n\n"+
		"If you wish to report any bugs, send in any praise, or otherwise communicate with Dicey and his creators, " +
		"please send an email to dicey@area42games.com.  We hope you enjoy your day.";

	private static final String donate_message = 
		"Dicey runs on Google's Appspot application hosting and if the daily quota gets exceeded then Dicey would be shut down for a time.\n\n  " +
		"If you wish to help out, you can visit following link and use the Donate button to donate to the Dicey Development Fund through PayPal." +
		"\n\n" +
		"http://www.area42games.com/drupal/dicey";

	//		"<form action=\"https://www.paypal.com/cgi-bin/webscr\" method=\"post\">" +
	//		"<input type=\"hidden\" name=\"cmd\" value=\"_s-xclick\">" +
	//		"<input type=\"hidden\" name=\"hosted_button_id\" value=\"10590397\">" +
	//		"<input type=\"image\" src=\"https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif\" border=\"0\" name=\"submit\" alt=\"PayPal - The safer, easier way to pay online!\">" +
	//		"<img alt=\"\" border=\"0\" src=\"https://www.paypal.com/en_US/i/scr/pixel.gif\" width=\"1\" height=\"1\">" +
	//		"</form>";

	private static final Logger log = Logger.getLogger(DiceServlet.class.getName());

	public String help()
	{		
		return help_message;
	}

	public String donate()
	{
		return donate_message;
	}

	public String roll(EvalContext context, String function)
	{
		return DiceParserDriver.parse(context,function);
	}

	public String reference(Wavelet wavelet, String function)
	{
		return "Found Reference "+wavelet.getDataDocument(function);
	}

	public String set(EvalContext context, String function)
	{
		String args[] = function.split("=");

		if(args.length == 2)
		{
//			if( !args[0].startsWith("$") )
//			{
//				args[0] = "$"+args[0];
//			}
			context.set(args[0].trim(), args[1].trim());

			return args[0].trim() + " = " +args[1].trim();
		}

		return "Invalid : \"Set("+function+"\")";
	}

	private void initData(Wavelet wavelet)
	{
		if(wavelet.getDataDocument("Area42Dice.Debug") == null)
		{
			wavelet.setDataDocument("Area42Dice.Debug","FALSE");
		}

		if(wavelet.getDataDocument("Area42Dice.MessageNumber") == null)
		{
			wavelet.setDataDocument("Area42Dice.MessageNumber", "0");
		}		

		if(wavelet.getDataDocument("Area42Dice.Style") == null)
		{
			wavelet.setDataDocument("Area42Dice.Style","Reply");
		}
	}

	private void logEvents(Wavelet wavelet)
	{
		String members = "";
		List<String> parts = wavelet.getParticipants();
		for(String s : parts)
		{
			members += s + ", ";
		}			
		members = members.substring(0,members.length()-2);
		log.info("Wave Members: "+members);
	}

	public void processEvents(RobotMessageBundle bundle)
	{
		Wavelet wavelet = bundle.getWavelet();

		initData(wavelet);
		logEvents(wavelet);

		boolean debug = wavelet.getDataDocument("Area42Dice.Debug").equals("TRUE");
		Integer messageNumber = Integer.parseInt(wavelet.getDataDocument("Area42Dice.MessageNumber"));
		log.info("Area42Dice.MessageNumber = "+messageNumber);		

		if(wavelet.getDataDocument("Area42Dice.Debug").equals("TRUE"))
		{
			Map<String, String> data = wavelet.getDataDocuments();
			for(String key : data.keySet())
			{
				if(key.startsWith("Area42Dice."))
					log.info(key + " = " + data.get(key));
			}
		}

		String rollCount = wavelet.getDataDocument("Area42Dice.Roll.Count");
		if(rollCount == null)
		{
			rollCount = "0";
		}

		int rollCountInt = Integer.parseInt(rollCount);

		if(bundle.wasSelfAdded())
		{
			if(messageNumber == 0)
			{
				Blip blip = wavelet.appendBlip();
				TextView textview = blip.getDocument();
				textview.append(
						"Dice Roller provided courtesy of Area 42 Games (http://www.area42games.com/)\n\n" +
				"Commands -- Help(), Donate(), Feedback(), Roll() and Set().  If you need more help, type \"Help()\" in a wave and submit it.");

				wavelet.setDataDocument("Area42Dice.MessageArea",blip.getBlipId());
				messageNumber++;
				wavelet.setDataDocument("Area42Dice.MessageNumber", ""+messageNumber);
			}
		}

		for (Event e: bundle.getEvents()) 
		{
			if (e.getType() == EventType.BLIP_SUBMITTED && !e.getModifiedBy().equals("area42dice@appspot.com")) 
			{
				Blip parent = e.getBlip();
				String text = " "+parent.getDocument().getText()+" ";

				List<String> help = DiceParserDriver.findStatements(DiceParserDriver.helpP, text);
				List<String> donate = DiceParserDriver.findStatements(DiceParserDriver.donateP, text);
				List<String> rolls = DiceParserDriver.findStatements(DiceParserDriver.rollP, text);
				List<String> refs = DiceParserDriver.findStatements(DiceParserDriver.refP, text);
				List<String> sets = DiceParserDriver.findStatements(DiceParserDriver.setP, text);
				List<String> prolls = DiceParserDriver.findStatements(DiceParserDriver.privP, text);
				List<String> feedback = DiceParserDriver.findStatements(DiceParserDriver.feedbackP, text);

				String newText = "";				

				int count = 0;

				logRequests(help,donate,rolls,refs,sets,prolls,feedback);

				if(help.size() > 0)
				{
					Blip blip = parent.createChild();
					TextView textView = blip.getDocument();
					textView.append(help());
				}

				if(donate.size() > 0)
				{
					Blip blip = parent.createChild();
					TextView textView = blip.getDocument();
					textView.append(donate());
					//					textView.appendMarkup(donate());
				}

				EvalContext context = new EvalContext(wavelet,e.getModifiedBy());

				for(String expr : sets)
				{
					String answer = set(context,expr);
					newText += " " + answer;
					count++;
					if(debug)
					{
					}
				}

				for(String expr : refs)
				{
					String answer = reference(wavelet,expr);
					if(debug)
					{
						newText += " " + answer;
						count++;
					}
				}

				for(String expr : rolls)
				{
					newText += " " + roll(context, expr);
					rollCountInt++;
					count++;
				}

				if(count > 0)
				{
					if(wavelet.getDataDocument("Area42Dice.Style").equals("Inline"))
					{
						parent.getDocument().append("\n");
						parent.getDocument().append(newText);
					}
					else
					{
						Blip blip = parent.createChild();
						TextView textView = blip.getDocument();
						textView.append(newText);
					}
				}

				if(prolls.size() > 0)
				{
					String privateText = "";

					for(String expr : prolls)
					{
						privateText += " " + roll(context, expr);
						rollCountInt++;
					}

					List<String> newParticipants = new ArrayList<String>();
					newParticipants.add(e.getModifiedBy());
					Wavelet newWavelet = bundle.createWavelet(newParticipants);
					newWavelet.getRootBlip().getDocument().append(privateText);
				}

				if(feedback.size() > 0)
				{
					String privateText = "Dicey Feedback";

					List<String> newParticipants = new ArrayList<String>();
					newParticipants.add(e.getModifiedBy());
					newParticipants.add("jhines0042@googlewave.com");
					Wavelet newWavelet = bundle.createWavelet(newParticipants);
					newWavelet.setTitle(privateText);					
				}

				wavelet.setDataDocument("Area42Dice.Roll.Count", ""+rollCountInt);

				log.info(newText);
			}
		}
	}

	private void logRequests(List<String> help, List<String> donate,
			List<String> rolls, List<String> refs,
			List<String> sets, List<String> prolls, List<String> feedback) 
	{
		if(help.size() > 0)
		{
			log.info("Help()");
		}

		if(donate.size() > 0)
		{
			log.info("Donate()");
		}

		if(feedback.size() > 0)
		{
			log.info("Feedback()");
		}

		for(String expr : sets)
		{
			log.info("Set("+expr+")\n");
		}

		for(String expr : refs)
		{
			log.info("Ref("+expr+")\n");
		}

		for(String expr : rolls)
		{
			log.info("Roll("+expr+")");
		}

		for(String expr : prolls)
		{
			log.info("Priv("+expr+")");
		}		
	}

	private Blip getBlipByID(String blipID, Wavelet wavelet)
	{
		Blip root = wavelet.getRootBlip();

		if(root.getBlipId().equals(blipID))
		{
			return root;
		}
		else
		{
			return getChildBlipByID(blipID,root);
		}
	}

	private Blip getChildBlipByID(String blipID, Blip parent)
	{
		List<Blip> children = parent.getChildren();

		for(Blip child : children)
		{
			if(child.getBlipId().equals(blipID))
			{
				return child;
			}
		}

		for(Blip child : children)
		{
			Blip retval = getChildBlipByID(blipID, child);
			if(retval != null)
			{
				return retval;
			}
		}		

		return null;
	}
}
